InGameHintsExample.cs (9384B)
1 // This example demonstrates how to display text in the UI that involves action bindings. 2 // When the player switches control schemes or customizes controls (the latter is not set up 3 // in this example but if supported, would work with the existing code as is), text that 4 // is shown to the user may be affected. 5 // 6 // In the example, the player is able to move around the world and look at objects (simple 7 // cubes). When an object is in sight, the player can pick the object with a button. While 8 // having an object picked up, the player can then either throw the object or drop it back 9 // on the ground. 10 // 11 // Depending on the current context, we display hints in the UI that reflect the currently 12 // active bindings. 13 14 using UnityEngine.UI; 15 16 namespace UnityEngine.InputSystem.Samples.InGameHints 17 { 18 public class InGameHintsExample : MonoBehaviour 19 { 20 public Text helpText; 21 public float moveSpeed; 22 public float rotateSpeed; 23 public float throwForce; 24 public float pickupDistance; 25 public float holdDistance; 26 27 private Vector2 m_Rotation; 28 29 private enum State 30 { 31 Wandering, 32 ObjectInSights, 33 ObjectPickedUp 34 } 35 36 private PlayerInput m_PlayerInput; 37 private State m_CurrentState; 38 private Transform m_CurrentObject; 39 private MaterialPropertyBlock m_PropertyBlock; 40 41 // Cached help texts so that we don't generate garbage all the time. Could even cache them by control 42 // scheme to not create garbage during control scheme switching but we consider control scheme switches 43 // rare so not worth the extra cost in complexity and memory. 44 private string m_LookAtObjectHelpText; 45 private string m_ThrowObjectHelpText; 46 47 private const string kDefaultHelpTextFormat = "Move close to one of the cubes and look at it to pick up"; 48 private const string kLookAtObjectHelpTextFormat = "Press {pickup} to pick object up"; 49 private const string kThrowObjectHelpTextFormat = "Press {throw} to throw object; press {drop} to drop object"; 50 51 public void Awake() 52 { 53 m_PlayerInput = GetComponent<PlayerInput>(); 54 } 55 56 public void OnEnable() 57 { 58 ChangeState(State.Wandering); 59 } 60 61 // This is invoked by PlayerInput when the controls on the player change. If the player switches control 62 // schemes or keyboard layouts, we end up here and re-generate our hints. 63 public void OnControlsChanged() 64 { 65 UpdateUIHints(regenerate: true); // Force re-generation of our cached text strings to pick up new bindings. 66 } 67 68 private int m_UpdateCount; 69 70 public void Update() 71 { 72 var move = m_PlayerInput.actions["move"].ReadValue<Vector2>(); 73 var look = m_PlayerInput.actions["look"].ReadValue<Vector2>(); 74 75 Move(move); 76 Look(look); 77 78 switch (m_CurrentState) 79 { 80 case State.Wandering: 81 case State.ObjectInSights: 82 // While looking around for an object to pick up, we constantly raycast into the world. 83 if (Physics.Raycast(transform.position, transform.forward, out var hitInfo, 84 pickupDistance) && !hitInfo.collider.gameObject.isStatic) 85 { 86 if (m_CurrentState != State.ObjectInSights) 87 ChangeState(State.ObjectInSights); 88 m_CurrentObject = hitInfo.transform; 89 90 // Set a custom color override on the object by installing our property block. 91 if (m_PropertyBlock == null) 92 { 93 m_PropertyBlock = new MaterialPropertyBlock(); 94 m_PropertyBlock.SetColor("_Color", new Color(0.75f, 0, 0)); 95 } 96 m_CurrentObject.GetComponent<MeshRenderer>().SetPropertyBlock(m_PropertyBlock); 97 } 98 else if (m_CurrentState != State.Wandering) 99 { 100 // No longer have object in sight. 101 ChangeState(State.Wandering); 102 103 if (m_CurrentObject != null) 104 { 105 // Clear property block on renderer to get rid of our custom color override. 106 m_CurrentObject.GetComponent<Renderer>().SetPropertyBlock(null); 107 m_CurrentObject = null; 108 } 109 } 110 111 if (m_PlayerInput.actions["pickup"].triggered && m_CurrentObject != null) 112 { 113 PickUp(); 114 ChangeState(State.ObjectPickedUp); 115 } 116 break; 117 118 case State.ObjectPickedUp: 119 // If the player hits the throw button, throw the currently carried object. 120 // For this example, let's call this good enough. In a real game, we'd want to avoid the raycast 121 if (m_PlayerInput.actions["throw"].triggered) 122 { 123 Throw(); 124 ChangeState(State.Wandering); 125 } 126 else if (m_PlayerInput.actions["drop"].triggered) 127 { 128 Throw(drop: true); 129 ChangeState(State.Wandering); 130 } 131 break; 132 } 133 } 134 135 private void ChangeState(State newState) 136 { 137 switch (newState) 138 { 139 case State.Wandering: 140 break; 141 case State.ObjectInSights: 142 break; 143 case State.ObjectPickedUp: 144 break; 145 } 146 147 m_CurrentState = newState; 148 UpdateUIHints(); 149 } 150 151 private void UpdateUIHints(bool regenerate = false) 152 { 153 if (regenerate) 154 { 155 m_ThrowObjectHelpText = default; 156 m_LookAtObjectHelpText = default; 157 } 158 159 switch (m_CurrentState) 160 { 161 case State.ObjectInSights: 162 if (m_LookAtObjectHelpText == null) 163 m_LookAtObjectHelpText = kLookAtObjectHelpTextFormat.Replace("{pickup}", 164 m_PlayerInput.actions["pickup"].GetBindingDisplayString()); 165 helpText.text = m_LookAtObjectHelpText; 166 break; 167 168 case State.ObjectPickedUp: 169 if (m_ThrowObjectHelpText == null) 170 m_ThrowObjectHelpText = kThrowObjectHelpTextFormat 171 .Replace("{throw}", m_PlayerInput.actions["throw"].GetBindingDisplayString()) 172 .Replace("{drop}", m_PlayerInput.actions["drop"].GetBindingDisplayString()); 173 helpText.text = m_ThrowObjectHelpText; 174 break; 175 176 default: 177 helpText.text = kDefaultHelpTextFormat; 178 break; 179 } 180 } 181 182 // Throw or drop currently picked up object. 183 private void Throw(bool drop = false) 184 { 185 // Unmount it. 186 m_CurrentObject.parent = null; 187 188 // Turn physics back on. 189 var rigidBody = m_CurrentObject.GetComponent<Rigidbody>(); 190 rigidBody.isKinematic = false; 191 192 // Apply force. 193 if (!drop) 194 rigidBody.AddForce(transform.forward * throwForce, ForceMode.Impulse); 195 196 m_CurrentObject = null; 197 } 198 199 private void PickUp() 200 { 201 // Mount to our transform. 202 m_CurrentObject.position = default; 203 m_CurrentObject.SetParent(transform, worldPositionStays: false); 204 m_CurrentObject.localPosition += new Vector3(0, 0, holdDistance); 205 206 // Remove color override. 207 m_CurrentObject.GetComponent<Renderer>().SetPropertyBlock(null); 208 209 // We don't want the object to be governed by physics while we hold it so turn it into a 210 // kinematics body. 211 m_CurrentObject.GetComponent<Rigidbody>().isKinematic = true; 212 } 213 214 private void Move(Vector2 direction) 215 { 216 if (direction.sqrMagnitude < 0.01) 217 return; 218 var scaledMoveSpeed = moveSpeed * Time.deltaTime; 219 // For simplicity's sake, we just keep movement in a single plane here. Rotate 220 // direction according to world Y rotation of player. 221 var move = Quaternion.Euler(0, transform.eulerAngles.y, 0) * new Vector3(direction.x, 0, direction.y); 222 transform.position += move * scaledMoveSpeed; 223 } 224 225 private void Look(Vector2 rotate) 226 { 227 if (rotate.sqrMagnitude < 0.01) 228 return; 229 var scaledRotateSpeed = rotateSpeed * Time.deltaTime; 230 m_Rotation.y += rotate.x * scaledRotateSpeed; 231 m_Rotation.x = Mathf.Clamp(m_Rotation.x - rotate.y * scaledRotateSpeed, -89, 89); 232 transform.localEulerAngles = m_Rotation; 233 } 234 } 235 }